#ifndef CARD_PARSE_HH__
#define CARD_PARSE_HH__

#include <string>
#include <map>
#include <iostream>
#include <cstring>

extern "C" {
#include <unistd.h>
}

struct country_code_lookup_table_t {
    std::string country;
    int code;
};

const static country_code_lookup_table_t country_codes[] = {
#include "country_codes.h"
};

struct CardData {
    std::map<std::string, std::string> industries;

    CardData () {
        industries["0"] = "Reserved for future use by ISO/TC 68";
        industries["00"] = "Institution other than card issuer";
        industries["1"] = "Airlines";
        industries["2"] = "Airlines and other future assignments";
        industries["3"] = "Travel and entertainment";
        industries["4"] = "Banking/Financial";
        industries["5"] = "Banking/Financial";
        industries["59"] = "Financial institutions not registered by ISO";
        industries["6"] = "Merchandising and banking";
        industries["7"] = "Petroleum";
        industries["8"] = "Telecom and other future assignments";
        industries["89"] = "Telecom admin and private operating agencies";
        industries["9"] = "Reserved for national use";
    }
    virtual int parse(const char *) = 0;
    virtual void parse_abort(const char * msg) {
        std::cerr << "Error while parsing card data:" << std::endl;
        std::cerr << "Message: " << msg << std::endl;
        _exit (1);
    }
    virtual void warn (const char * msg) {
        std::cerr << " <<< WARNING >>> " << msg << std::endl;
    }
    virtual bool valid_luhn_check (std::string &acct, char c) {
        std::cerr << "Luhn check input: " << acct << std::endl;
        int luhn = c - '0', i = 1, sum = 0;
        std::string::reverse_iterator rit = acct.rbegin();
        ++rit; // dont include the check in the calculation
        for (; rit != acct.rend(); ++rit, ++i) {
            std::cerr << "  " << *rit << std::endl;
            if (i & 1) {
                int x = *rit - '0';
                x *= 2;
                sum += (x / 10) + (x % 10);
            } else {
                sum += *rit - '0';
            }
        }
        return luhn == (sum % 10);
    }
    virtual void trace (const char * msg) {
        std::cerr << " <<  NOTICE   >> " << msg << std::endl;
    }

};

/* Format: 
        % - Begin sentinel
        [Format Code] : 1 character
        [Primary Account Number] : up to 19 digits
        ^ - Field separator
        [Country Code] : 3 characters (only if PAN is 59)
        [Name] : 2-26 characters
        ^ - Field separator
        [Expiration Date] : 4 digits (YYMM) { could be ^ when unused }
        [Service Code] : 3 digits
        [Pin Verification Value] : 5 digits
        [Discretionary Data] : Remainder of data
        ? - End sentinel
*/
struct TrackOne : public CardData {

    unsigned char format_code, luhn_check;
    std::string primary_account, issuer, major_industry, issuer_id;
    std::string individual_account;

    int parse (const char * input) {
        int idx = 0;
        int in_len = ::strlen (input);
        if (! input) 
            parse_abort ("TrackOne::parse : nil input");
        if (*input != '%') 
            parse_abort ("TrackOne::parse : missing start sentinel");
        if (in_len < 4)
            parse_abort ("TrackOne::parse : invalid input contents");
        format_code = input[++idx];
        // TODO : Branch based on format code B vs. N
        std::cerr << "Format Code: " << format_code << std::endl;
        while (idx < (in_len - 1) && input[++idx] != '^')
            primary_account += input[idx];
        if (idx == in_len - 1) 
            parse_abort ("TrackOne::parse : invalid primary account length");
        parse_primary_account (primary_account);
        return idx;
    }

    bool contains (const char * chunk) {
        // TODO : Fill me in
        return true;
    }

    void parse_primary_account (std::string &acct) {
        int i = 2, acct_len = acct.size();

        // Handle 00, 59 and 89 special
        if (acct[0] == '0' && acct[1] == '0')
            major_industry = industries["00"];
        else if (acct[0] == '5' && acct[1] == '9')
            major_industry = industries["59"];
        else if (acct[0] == '8' && acct[1] == '9')
            major_industry = industries["89"];
        else {
            major_industry = industries[acct.substr(0,1)];
            i = 1;
        }

        std::cerr << "Industry: " << major_industry << std::endl;

        // Check to see if the identifier could be a length specification:
        // We can only expect up to 6 digits for issuer
        int length_left = 6 - i, iid = acct[i] - '0';
        if (iid > length_left) {
            // not possibly a length - can we derive length from overall len?
            // up to 12 for account and 1 for check digit means
            // acct_len - 13 - i = remain for issuer_id
            int iid_len = acct_len - 13 - i;
            if (iid_len <= 0) {
                //not enough room - what to do?
                warn("Unable to determine Issuer Identifier length");
                warn("Skipping remainder of primary account parse");
                return;
            } else {
                // Just consume up to as many as we can 
                while (iid_len > 0) {
                    issuer_id += acct[i];
                    ++i; --iid_len;
                }
            } 
        } else {
            // this *could* be our length
            while (iid > 0) {
                issuer_id += acct[i];
                ++i; --iid;
            }
        }

        std::cerr << "Issuer ID: " << issuer_id << std::endl;
        
        // Capture the individual account number here
        while (i < acct.size() - 1) {
            individual_account += acct[i];
            ++i;
        }

        std::cerr << "Individual Account: " << individual_account << std::endl;
        luhn_check = acct[i];

        std::cerr << "Luhn Check : " << luhn_check << std::endl;

        if ( ! valid_luhn_check (acct, luhn_check)) 
            warn("Invalid Luhn check on the primary account");
    }
};

/* Format:
        ; - Start sentinel
        [Primary Account Number] : up to 19 digits
        = - Field separator
        [Country Code] : 3 characters (only if PAN is 59)
        [Expiration Date] : 4 digits (YYMM) { could be a = when unused }
        [Service Code] : 3 digits
        [Pin verification] : 5 digits
        [Discretionary Data] : Remainder of data
        ? - End sentinel
*/
struct TrackTwo : public CardData {
    bool contains (const char * chunk) {
        // TODO : Fill me in
        return true;
    }

    int parse (const char * input) { return 0; }
};

class Card {
    TrackOne track1_;
    TrackTwo track2_;
    std::string raw_data_;
public:
    Card (const char * input) {
        raw_data_ = std::string(input);
        track2_.parse(input + track1_.parse(input));
    }

    const TrackOne track_one () const { return track1_; }
    const TrackTwo track_two () const { return track2_; }

};

#endif //CARD_PARSE_HH__
